#include <string>
#include <iostream>
#include <sstream>
#include <assert.h>
#include <GL/glew.h>
#include "freeglut.h"
#include "ShaderSupport.h"

/*	OpenGL Shader Support Functions
	Perry Kivolowitz
	University of Wisconsin - Madison - Computer Sciences Department
*/

using namespace std;

// Graphics Shaders: Theory and Practice
void ShaderSupport::CheckGLErrors(string & caller)
{
	GLenum e = glGetError();

	if (e != GL_NO_ERROR)
	{
		switch (e)
		{
		case GL_INVALID_ENUM:
			cerr << caller + string(" Invalid ENUM") << endl;

		case GL_INVALID_VALUE:
			cerr << caller + string(" Invalid VALUE") << endl;

		case GL_INVALID_OPERATION:
			cerr << caller + string(" Invalid OPERATION") << endl;

		case GL_STACK_OVERFLOW:
			cerr << caller + string(" Stack OVERFLOW") << endl;

		case GL_STACK_UNDERFLOW:
			cerr << caller + string(" Stack UNDERFLOW") << endl;

		case GL_OUT_OF_MEMORY:
			cerr << caller + string(" Out of memory") << endl;
		}
	}
}

// Based upon Graphics Shaders: Theory and Practice
// Modified by PK to throw exceptions.
void ShaderSupport::LoadShader(const char * file_name, GLuint shader_id)
{
	//CheckGLErrors(string("LoadShader ") + string(file_name));
	assert(file_name != NULL);
	FILE * file_handle = NULL;
	fopen_s(&file_handle, file_name, "rb");
	if (file_handle == NULL)
		throw string("Cannot open shader: ") + string(file_name);
	fseek(file_handle, 0, SEEK_END);
	int length = ftell(file_handle);
	fseek(file_handle, 0, SEEK_SET);
	GLubyte * buffer = new GLubyte[length + 1];
	fread(buffer, sizeof(GLubyte), length, file_handle);
	fclose(file_handle);
	buffer[length] = 0;
	glShaderSource(shader_id, 1, (const char **) &buffer, NULL);
	delete [] buffer;
	CheckGLErrors(string("LoadShader ") + string(file_name));
}

stringstream ShaderSupport::GetShaderLog(GLuint shader_id)
{
	stringstream s;
	GLint log_length;
	glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_length);
	if (log_length == 0)
		s << "No shader log information available." << endl;
	else
	{
		GLchar * buffer = new GLchar[log_length];
		glGetShaderInfoLog(shader_id, log_length, NULL, buffer);
		s << "Shader log:" << endl;
		s << buffer << endl;
		delete [] buffer;
	}
	return s;
}

stringstream ShaderSupport::GetGLInfo()
{
	// Based upon OpenGL 4.0 Shading Language Cookbook
	// David Wolff
	GLint major_version , minor_version;
	glGetIntegerv(GL_MAJOR_VERSION, &major_version);
	glGetIntegerv(GL_MINOR_VERSION, &minor_version);
	stringstream s;
	s << "OpenGL info:" << endl;
	s << "Renderer: " << glGetString(GL_RENDERER) << endl;
	s << "Vendor: " << glGetString(GL_VENDOR) << endl;
	s << "GL Version: " << glGetString(GL_VERSION) << " Major Version: " << major_version << " Minor Version: " << minor_version << endl;
	s << "GLSL Version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << endl;
	return s;
}


ShaderHandles::ShaderHandles()
{
	vertex_shader_id = -1;
	fragment_shader_id = -1;
	program_id = -1;
}

void ShaderHandles::InitializeShader(string vertex_shader_file, string fragment_shader_file)
{
	GLint check_value;

	ShaderSupport::CheckGLErrors(string("InitializeShader: 1"));

	vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);

	ShaderSupport::CheckGLErrors(string("InitializeShader: 2"));

	ShaderSupport::LoadShader(vertex_shader_file.c_str(), vertex_shader_id);
	glCompileShader(vertex_shader_id);
	glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &check_value);
	if (check_value != GL_TRUE)
	{
		cerr << ShaderSupport::GetShaderLog(vertex_shader_id).str();
		throw std::string("GLSL compilation failed - vertex shader: ") + vertex_shader_file;
	}
	fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
	ShaderSupport::LoadShader(fragment_shader_file.c_str(), fragment_shader_id);
	glCompileShader(fragment_shader_id);
	glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &check_value);
	if (check_value != GL_TRUE)
	{
		cerr << ShaderSupport::GetShaderLog(fragment_shader_id).str();
		throw std::string("GLSL compilation failed - fragment shader: ") + fragment_shader_file;
	}
	program_id = glCreateProgram();
	glAttachShader(program_id, vertex_shader_id);
	glAttachShader(program_id, fragment_shader_id);
	InitializeMappings();
	glLinkProgram(program_id);
	glDeleteShader(vertex_shader_id);
	glDeleteShader(fragment_shader_id);
	InitializeUniforms();
	ShaderSupport::CheckGLErrors(string("InitializeShader: ") + vertex_shader_file + string(" ") + fragment_shader_file);
}

void ShaderHandles::TakeDown()
{
	GLint temp;
	GLsizei size;

	glGetProgramiv(program_id, GL_ATTACHED_SHADERS, &temp);
	if (temp > 0)
	{
		GLuint * shader_list = new GLuint[temp];
		glGetAttachedShaders(program_id, temp, &size, shader_list);
		for (GLsizei i = 0; i < size; i++)
		{
			glDetachShader(program_id, shader_list[i]);
			// The shaders were marked for deletion
			// immediately after they were created.
		}
		delete [] shader_list;
	}
	glDeleteProgram(program_id);

	program_id = 0;
}

void ShaderHandles::UseProgram()
{
	assert(program_id != -1);
	glUseProgram(program_id);
}
